package picoevo.app.chemicalreactions;

import java.util.ArrayList;

import picoevo.tools.Display;
import picoevo.tools.Utils;

public class Reaction implements Cloneable {

	protected int leftEnzymeId;
	protected int leftProductOneId;
	protected int leftProductTwoId;
	protected int rightEnzymeId;
	protected int rightProductOneId;
	protected int rightProductTwoId;
	
	protected double k_minus; // non-enzymatic reaction
	protected double k_plus; // non-enzymatic reaction
	protected double k_i; //Ftagn //Un Ki par enzyme présente
	
	public Reaction()
	{
		init();
	}
	
	public Reaction(boolean __init)  // used by ODESimulation project (utils.IndividualGenerator)
	{
		if(__init)
			init();
	}
	
	public int[] getSignature()
	{
		int[] signature = new int[6];
		signature[0] = leftEnzymeId;
		signature[1] = leftProductOneId;
		signature[2] = leftProductTwoId;
		signature[3] = rightEnzymeId;
		signature[4] = rightProductOneId;
		signature[5] = rightProductTwoId;
		return signature;
	}
	
	public void setSignature(int[] signature)
	{
		leftEnzymeId = signature[0];
		leftProductOneId = signature[1];
		leftProductTwoId = signature[2];
		rightEnzymeId = signature[3];
		rightProductOneId = signature[4];
		rightProductTwoId = signature[5];
				
		// DEBUG
		/*
		if ( checkConsistency( ) == false )
		{
			Display.debug("[setSign(...)] CONSISTENCY ERROR!!!");
			System.exit(-1);
		}
		else
			Display.debug("[setSign(...)] Consistency OK.");
		/**/
		// DEBUG
	}
	
	public void setSignature(int[] signature, double __k_plus, double __k_minus)
	{
		setSignature(signature);
		this.k_plus = __k_plus;
		this.k_minus = __k_minus;
	}
	
	public Object clone ()
	{
		Reaction reactionClone = null;
		try 
		{
			reactionClone = (Reaction)super.clone();
		} catch (CloneNotSupportedException e) 
		{
			e.printStackTrace();
		}
		return reactionClone;
	}
	public double getKi()
	{
		return k_i;
	}//Ftagn

	public void init()
	{
		if ( Math.random() < Global_CR.P_init_enzymaticReaction )
		{
			// enzymatic reaction
			//Ftagn
			k_i = Math.random(); 
			
			leftEnzymeId = (int) ( Math.random() * Global_CR.enzymes.length );
			rightEnzymeId = leftEnzymeId;
		
			leftProductOneId = (int) ( Math.random() *  Global_CR.products.length );
			
			leftProductTwoId = -1;
			
			if ( Math.random() < Global_CR.P_init_rightProductOne_enzymaticReaction_waste )
				rightProductOneId = -1;
			else
				rightProductOneId = (int) ( Math.random() * Global_CR.products.length );
			
			if ( rightProductOneId != -1 ) // restriction
			{
				if ( Math.random() < Global_CR.P_init_rightProductTwo_enzymaticReaction_waste )
					rightProductTwoId = -1;
				else
					rightProductTwoId = (int) ( Math.random() * Global_CR.products.length ); 
			}
			else
				rightProductTwoId = -1;
		}
		else 
		{
			// non-enzymatic reaction
			
			initKvalues();

			leftEnzymeId = -1;
			rightEnzymeId = -1;

			if ( Math.random() < Global_CR.P_init_leftProductOne_simpleReaction_waste )
				leftProductOneId = -1;
			else
				leftProductOneId = (int) ( Math.random() * Global_CR.products.length );
			
			if ( leftProductOneId != -1 )
			{
				if ( Math.random() < Global_CR.P_init_leftProductTwo_simpleReaction_waste )
					leftProductTwoId = -1;
				else
					leftProductTwoId = (int) ( Math.random() * Global_CR.products.length );
			}
			else
				leftProductTwoId = -1;

			if ( Math.random() < Global_CR.P_init_rightProductOne_simpleReaction_waste )
				rightProductOneId = -1;
			else
				rightProductOneId = (int) ( Math.random() * Global_CR.products.length );
			
			if ( rightProductOneId != -1 ) // restriction
			{
				if ( Math.random() < Global_CR.P_init_rightProductTwo_simpleReaction_waste )
					rightProductTwoId = -1;
				else
					rightProductTwoId = (int) ( Math.random() * Global_CR.products.length ); 
			}
			else
				rightProductTwoId = -1;
			
		}
		
		// ensure consistency (ie. avoid similar left and right sides in non-enzymatic reaction)
		while ( !checkConsistency() )
		{
			if ( Math.random() < Global_CR.P_init_leftProductOne_simpleReaction_waste )
				leftProductOneId = -1;
			else
				leftProductOneId = (int) ( Math.random() * Global_CR.products.length );
		}		
		
		
		// DEBUG
		/*
		if ( checkConsistency( ) == false )
		{
			Display.debug("INIT: CONSISTENCY ERROR!!!");
			System.exit(-1);
		}
		else
			Display.debug("INIT: Consistency OK.");
		/**/
		// DEBUG

		
	}
	
	private void initKvalues() 
	{
		if ( Math.random() < Global_CR.P_init_mutate_silence_Kminus )
			k_minus = 0;
		else
			k_minus = Utils.getRandomPow(Global_CR.KminusMinPowValue,Global_CR.KminusMaxPowValue);

		if ( k_minus != 0 && Math.random() < Global_CR.P_init_mutate_silence_Kplus )
			k_plus = 0;
		else
			k_plus = Utils.getRandomPow(Global_CR.KplusMinPowValue,Global_CR.KplusMaxPowValue);
	}

	
	public String toString()
	{
		String s = "";
		if ( leftEnzymeId == -1 )
		{
			// non-enzymatic reaction
			if ( leftProductOneId != -1 )
				s += Global_CR.products[leftProductOneId];
			if ( leftProductOneId >= 0 && leftProductTwoId >= 0 )
				s += " + "; 
			if ( leftProductTwoId >= 0 )
				s += Global_CR.products[leftProductTwoId];
			
			s += " ";
			if ( this.k_minus != 0 )
				s += "<";
			s += "==";
			if ( this.k_plus != 0 )
				s += ">";
			s += " ";
			
			if ( rightProductOneId != -1 )
				s += Global_CR.products[rightProductOneId];
			if ( rightProductOneId != -1 && rightProductTwoId != -1 )
				s += " + "; 
			if ( rightProductTwoId != -1 )
				s += Global_CR.products[rightProductTwoId];
			s += " [ k+ = " + k_plus + " ; k- = " + k_minus + " ]";
		}
		else
		{
			// enzymatic reaction
			//s += Global.enzymes[leftEnzymeId] + " + " + Global.products[leftProductOneId] + " ==> " + Global.enzymes[rightEnzymeId] + " + " + Global.products[rightProductOneId];
			
			if ( leftProductOneId == -1 ) // DEBUG
			{
				s += Global_CR.enzymes[leftEnzymeId] + " + ";
				s += "ERROR";
				s += " ==> " + Global_CR.enzymes[rightEnzymeId];
				if ( rightProductOneId >= 0)
					s += " + " + Global_CR.products[rightProductOneId];
				if ( rightProductTwoId >= 0)
					s += " + " + Global_CR.products[rightProductTwoId];
				Display.debug(s);
				System.exit(0);
			}
			
			s += Global_CR.enzymes[leftEnzymeId] + " + ";
			s += Global_CR.products[leftProductOneId];
			s += " ==> " + Global_CR.enzymes[rightEnzymeId];
			if ( rightProductOneId >= 0)
				s += " + " + Global_CR.products[rightProductOneId];
			if ( rightProductTwoId >= 0)
				s += " + " + Global_CR.products[rightProductTwoId];
		}
		return s;
	}

	public boolean checkConsistency()
	{
		if ( leftEnzymeId == -1 )
		{
			if ( ( leftProductOneId == rightProductOneId && leftProductTwoId == rightProductTwoId ) || ( leftProductOneId == rightProductTwoId && leftProductTwoId == rightProductOneId ) || ( leftProductOneId == -1 && leftProductTwoId == -1 && rightProductOneId == -1 && rightProductTwoId == -1 ) )
			{
				return false;
			}
		}
		return true;
	}

	static public boolean checkConsistency( int[] signature )
	{
		// DEBUG
		/*
		if ( signature[1] == -1 && signature[5] == -1 && signature[2] == -1 && signature[4] == -1 )
		{
			Display.debug("ERROR\nsignature:");
			for ( int i=0 ; i!=6 ; i++) 
				Display.debug("- signature["+i+"]: "+ signature[i]);
			Display.debug("");
			System.exit(-1);
		}
		/**/
		// DEBUG
		
		if ( signature[0] == -1 )
		{
			if ( ( signature[1] == signature[4] && signature[2] == signature[5] ) || ( signature[1] == signature[5] && signature[2] == signature[4] ) || ( signature[1] == -1 && signature[2] == -1 && signature[4] == -1 && signature[5] == -1 ) )
			{
				return false;
			}
		}

		return true;
	}
	
	public boolean isEnzymaticReaction()
	{
		if ( this.leftEnzymeId == -1 )
			return false;
		else 
			return true;
	}
	
	// =-=-=-=-=-=-=-=-=-=-=-=-=
	// =-=-=-=-=-=-=-=-=-=-=-=-=
	// =-=-=-=-=-=-=-=-=-=-=-=-=
	
	private int getEmptySlotCnt()
	{
		// count nb of slots without a product
		int emptySlotCnt = 0;
		if ( this.leftProductOneId == -1 )
			emptySlotCnt++;
		if ( this.leftProductTwoId == -1 && leftEnzymeId == -1)
			emptySlotCnt++;
		if ( this.rightProductOneId == -1 )
			emptySlotCnt++;
		if ( this.rightProductTwoId == -1 )
			emptySlotCnt++;
		return emptySlotCnt;
	}
	
	private int getNonEmptySlotCnt()
	{		
		int filledSlotCnt, emptySlotCnt;
		emptySlotCnt = getEmptySlotCnt();
		if ( leftEnzymeId == -1 )
			filledSlotCnt = 4 - emptySlotCnt;
		else
			filledSlotCnt = 3 - emptySlotCnt; // exception: does not consider secondary left product if enzymatic reaction.
		return filledSlotCnt;
	}
	
	private boolean mutateProductAdd()
	{
		// count nb of empty slots (check: primary or secondary)
		// pick one slot (if any)
		// set new product

		int emptySlotCnt = getEmptySlotCnt();
		
		int[] signature = getSignature();
		int[] target = new int[emptySlotCnt];
		int index = 0;
		
		if ( signature[1] == -1 )
		{
			target[index] = 1;
			index++;
		}
		if ( signature[2] == -1 && leftEnzymeId == -1)
		{
			target[index] = 2;
			index++;
		}
		if ( signature[4] == -1 )
		{
			target[index] = 4;
			index++;
		}
		if ( signature[5] == -1 )
		{
			target[index] = 5;
			index++;
		}

		int targetIndex = (int)( Math.random() * index );
		
		do
		{
			signature[target[targetIndex]] = (int) ( Math.random() * Global_CR.products.length );
		}
		while ( !checkConsistency(signature) );

		if ( checkConsistency(signature) == true )
		{
			this.setSignature(signature);
			return true;
		}
		else
		{
			Display.warning("Reaction::mutateProductAdd - ignore because non-consistent result");
			return false;
		}
	}
	

	private boolean mutateProductRemove()
	{
		// count nb of non-empty slots (check: primary or secondary)
		// pick one slot (if any)
		// remove product

		int nonEmptySlotCnt = getNonEmptySlotCnt();
		
		int[] signature = getSignature();
		
		// DEBUG
		//Display.debug("mutate product remove");
		//for ( int i=0 ; i!=6 ; i++) 
		//	Display.debug("- signature["+i+"]: "+ signature[i]);
		// DEBUG
		
		int[] target = new int[nonEmptySlotCnt];
		int index = 0;
		
		if ( signature[1] != -1 && leftEnzymeId == -1)
		{
			target[index] = 1;
			index++;
		}
		if ( signature[2] != -1 && leftEnzymeId == -1)
		{
			target[index] = 2;
			index++;
		}
		if ( signature[4] != -1 )
		{
			target[index] = 4;
			index++;
		}
		if ( signature[5] != -1 )
		{
			target[index] = 5;
			index++;
		}
		
		// DEBUG
		/*
		Display.debug(" index="+index+ " ; target.length="+target.length + " ; signature.length="+signature.length);
		if ( signature[1] == -1 && signature[5] == -1 && signature[2] == -1 && signature[4] == -1 )
		{
			Display.debug("ERROR\nsignature:");
			for ( int i=0 ; i!=6 ; i++) 
				Display.debug("- signature["+i+"]: "+ signature[i]);
			Display.debug("");
		}
		/**/
		//DEBUG 

		int targetIndex = (int)( Math.random() * index );
		
		signature[target[targetIndex]] = -1;
		
		if ( checkConsistency(signature) == true )
		{
			this.setSignature(signature);
			return true;
		}
		else
		{
			Display.warning("Reaction::mutateProductRemove - ignore because non-consistent result");
			return false;
		}
	}


	private boolean mutateProductSwap()
	{
		// count nb of non-empty slots (check: primary or secondary)
		// pick one slot (if any)
		// swap product

		int nonEmptySlotCnt = getNonEmptySlotCnt();
		int[] signature = getSignature();
		int[] target = new int[nonEmptySlotCnt];
		int index = 0;

		// DEBUG
		/*
		Display.debug("mutate product swap");
		for ( int i=0 ; i!=6 ; i++) 
			Display.debug("- signature["+i+"]: "+ signature[i]);
		/**/
		// DEBUG
		
		if ( signature[1] != -1 )
		{
			target[index] = 1;
			index++;
		}
		if ( signature[2] != -1 && leftEnzymeId == -1)
		{
			target[index] = 2;
			index++;
		}
		if ( signature[4] != -1 )
		{
			target[index] = 4;
			index++;
		}
		if ( signature[5] != -1 )
		{
			target[index] = 5;
			index++;
		}

		// DEBUG
		/*
		Display.debug(" index="+index+ " ; target.length="+target.length + " ; signature.length="+signature.length);
		if ( signature[1] == -1 && signature[5] == -1 && signature[2] == -1 && signature[4] == -1 )
		{
			Display.debug("ERROR\nsignature:");
			for ( int i=0 ; i!=6 ; i++) 
				Display.debug("- signature["+i+"]: "+ signature[i]);
			Display.debug("");
		}
		/**/
		//DEBUG 
		
		int targetIndex = (int)( Math.random() * index );

		int oldId = signature[target[targetIndex]];
		int newId;
		do { 
			newId = (int)( Math.random() * Global_CR.products.length ); 
		} while ( newId == oldId );
		signature[target[targetIndex]] = newId;
		
		if ( checkConsistency(signature) == true )
		{
			this.setSignature(signature);
			return true;
		}
		else
		{
			Display.warning("Reaction::mutateProductSwap - ignore because non-consistent result");
			return false;
		}
	}
	
	/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
	
	public int mutateParameters() // => one mutation at most.
	{
		int mutations = 0;
		
		// parametric mutation --- this works only for non-enzymatic reaction
		// mutate k+ and k-: change value? set to zero?
		if ( this.leftEnzymeId == -1 )
		{
			double P_all = 0;
			
			if ( k_plus != 0 && k_minus != 0 )
				P_all = Global_CR.P_mutate_Kplus + Global_CR.P_mutate_Kminus + Global_CR.P_mutate_silence_Kminus + Global_CR.P_mutate_silence_Kplus;
			else
				P_all = Global_CR.P_mutate_Kplus + Global_CR.P_mutate_Kminus; // do not consider silencing (ie. there is already one parameter set to zero.
			
			double P_current = 0;

			double dice = Math.random();

			P_current += Global_CR.P_mutate_Kplus;
			if ( dice < P_current / P_all )
			{
				Display.info("Reaction: mutate k+.");
				mutations++;
				k_plus = Utils.getRandomPow(Global_CR.KplusMinPowValue,Global_CR.KplusMaxPowValue);
			}
			else 
			{
				P_current += Global_CR.P_mutate_Kminus;
				if ( dice < P_current / P_all )
				{
					Display.info("Reaction: mutate k-.");
					mutations++;
					k_minus = Utils.getRandomPow(Global_CR.KminusMinPowValue,Global_CR.KminusMaxPowValue);
				}
				else // should enter this block only if silencing is possible
				{
					if ( k_plus != 0 && k_minus != 0 ) // not necessary.
					{		
						P_current += Global_CR.P_mutate_silence_Kplus;
						if ( dice < Global_CR.P_mutate_silence_Kplus )
						{
							Display.info("Reaction: silence k+.");
							if ( k_minus != 0 )
							{
								k_plus = 0;
								mutations++;
							}
						}
						else
						{
							//P_current += Global_CR.P_mutate_Kminus;
							//if ( dice < Global_CR.P_mutate_silence_Kplus )
							{
								Display.info("Reaction: silence k-.");
								if ( k_plus != 0 )
								{
									k_minus = 0;
									mutations++;
								}
							}
						}
					}
				}
			}
		}
					
		return mutations;
	}
	
	
	// ---- ---- ----
	
	public int mutateStructure(Individual_CR i) // => one mutation at most.
	{
		int mutations = 0;
		
		// * init
		
		double P_all = 0;
		
		if ( getEmptySlotCnt() > 0 )
			P_all += Global_CR.P_mutate_product_add;
		if ( getNonEmptySlotCnt() > 1 )
			P_all += Global_CR.P_mutate_product_remove;
		if ( this.leftEnzymeId == -1 && this.leftProductOneId != -1 )
			P_all += Global_CR.P_mutate_enzyme_add;
		if ( this.leftEnzymeId != -1 )
			P_all += Global_CR.P_mutate_enzyme_remove;
		if ( Global_CR.enzymes.length > 1 && this.leftEnzymeId != -1 )
			P_all += Global_CR.P_mutate_enzyme_swap;
		
		double P_current = 0;

		double dice = Math.random();

		// * apply one mutation
		
		if ( getEmptySlotCnt() > 0 )
		{
			P_current += Global_CR.P_mutate_product_add;
			if ( dice < P_current / P_all )
			{
				// fill an empty slot with a product
				
				Display.info("Reaction: add a product.");
				
				if ( mutateProductAdd() )
					mutations++;
			}
		}
		else
		{
			if ( getNonEmptySlotCnt() > 1)
			{
				P_current += Global_CR.P_mutate_product_remove;
				if ( dice < P_current / P_all )
				{
					// remove one of the product
					
					Display.info("Reaction: remove a product");
					
					if ( mutateProductRemove() )
						mutations++;
				}
			}
			else
			{
				P_current += Global_CR.P_mutate_product_swap;
				if ( dice < P_current / P_all )
				{
					// swap value of one of the product
					
					Display.info("Reaction: swap a product.");
					
					if ( mutateProductSwap() ) 
						mutations++;
				}
				else
				{			
					if ( this.leftEnzymeId == -1 && this.leftProductOneId != -1 )
					{
						P_current += Global_CR.P_mutate_enzyme_add;
						if ( dice < P_current / P_all )
						{
							// add enzyme
							
							Display.info("Reaction: add an enzyme.");
				
							// check if non-enzymatic
							// if ok: add random enzyme (left, right), set random K_m and k_i
							//FGJ
							
							leftEnzymeId = (int) ( Math.random() * Global_CR.enzymes.length );
							if(k_minus == 0.0)
							{
								i.matrix_Km[leftEnzymeId][rightProductOneId] = Math.pow(10, Math.random()*4);
								i.matrix_ki[leftEnzymeId][rightProductOneId] = i.matrix_Km[leftEnzymeId][rightProductOneId] * k_plus; //FGJ GROS DOUTE
							}
							else
							{
								//FGJ
								
								/*
								 * Si passage de (non-enzymatique réversible) à (enzymatique):
								 * exemple: A<=>B devient A+E1=>B+E1 et B+E1=>A+E1
     							 * on construit deux réactions enzymatiques
     							 */
							}

							rightEnzymeId = leftEnzymeId;
							leftProductTwoId = -1;
							
							mutations++;
						}
					}
					else
					{
						if ( this.leftEnzymeId != -1 )
						{
							P_current += Global_CR.P_mutate_enzyme_remove;
							if ( dice < P_current / P_all )
							{
								// remove enzyme
								
								Display.info("Reaction: remove an enzyme.");
								k_plus = i.matrix_ki[leftEnzymeId][rightProductOneId]/i.matrix_Km[leftEnzymeId][rightProductOneId];
								k_minus = 0.0;
								//FGJ !!
								// check if enzymatic
								// if ok: remove enzyme (left, right)
								int[] signature = this.getSignature();
								signature[0] = -1;
								signature[3] = -1;
								if ( checkConsistency(signature) == true )
								{
									leftEnzymeId = -1;
									rightEnzymeId = -1;
									initKvalues();
					
									mutations++;
								}
								else
								{
									Display.warning("Reaction::mutateEnzymeRemove - ignore because non-consistent result");
								}
							}
						}
						else
						{
							if (  Global_CR.enzymes.length > 1 && this.leftEnzymeId != -1 )
							{
								P_current += Global_CR.P_mutate_enzyme_swap;
								if ( dice < P_current / P_all )
								{
									// swap enzyme
									
									Display.info("Reaction: swap an enzyme.");
									mutations++;
						
									// check if enzymatic
									// if ok: swap enzyme (must be different)			
									int newEnzymeId;
									do {
										newEnzymeId = (int) ( Math.random() * Global_CR.enzymes.length );
									} while ( newEnzymeId == leftEnzymeId );
									leftEnzymeId = newEnzymeId;
									rightEnzymeId = leftEnzymeId;			
								}
							}
						}
					}
				}
			}
		}
		
		return mutations;
	}

	// ---- ---- ---- 
	
	/*public int mutate(Individual_CR i)
	{	
		int mutations = 0;
		
		mutations += mutateParameters();
		
		mutations += mutateStructure(i);
		
		return mutations;
	}*/ //FGJ !

	/*public int mutateStructure(Individual_CR i) {
		// TODO Auto-generated method stub
		return 0;
	}*/
}
